// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Author: Markus Gutschke

#ifdef USE_NSS

#include <errno.h>
#include <netdb.h>
#include <nspr/prio.h>
#include <nspr/prerror.h>
#include <nss/nss.h>
#include <nss/secmod.h>
#include <nss/ssl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "check.h"
#include "ssl.h"

struct PRFilePrivate {
  int       f;
  PRNetAddr peername;
};

static PRStatus PR_CALLBACK fdioClose(PRFileDesc *fd) {
  free(fd->secret);
  fd->secret = NULL;
  return PR_SUCCESS;
}

static PRStatus PR_CALLBACK fdioShutdown(PRFileDesc *fd, PRIntn how) {
  return PR_SUCCESS;
}

static int PR_CALLBACK fdioRecv(PRFileDesc *fd, void *buf, PRInt32 len,
                                PRIntn flags, PRIntervalTime timeout) {
  if (flags || timeout != PR_INTERVAL_NO_TIMEOUT) {
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return -1;
  }
  ssize_t rc;
  do {
    rc = read(fd->secret->f, buf, len);
  } while (rc < 0 && errno == EINTR);
  if (rc < 0) {
    if (errno == EWOULDBLOCK) {
      PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
    } else {
      PR_SetError(PR_UNKNOWN_ERROR, 0);
    }
    return -1;
  }
  return rc;
}

static int PR_CALLBACK fdioRead(PRFileDesc *fd, void *buf, PRInt32 len) {
  return fdioRecv(fd, buf, len, 0, PR_INTERVAL_NO_TIMEOUT);
}

static int PR_CALLBACK fdioSend(PRFileDesc *fd, const void *buf, PRInt32 len,
                                PRIntn flags, PRIntervalTime timeout) {
  if (flags || timeout != PR_INTERVAL_NO_TIMEOUT) {
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return -1;
  }
  ssize_t rc;
  do {
    rc = write(fd->secret->f, buf, len);
  } while (rc < 0 && errno == EINTR);
  if (rc < 0) {
    if (errno == EWOULDBLOCK) {
      PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
    } else {
      PR_SetError(PR_UNKNOWN_ERROR, 0);
    }
    return -1;
  }
  return rc;
}

static int PR_CALLBACK fdioWrite(PRFileDesc *fd, const void *buf, PRInt32 len){
  return fdioSend(fd, buf, len, 0, PR_INTERVAL_NO_TIMEOUT);
}

static PRStatus PR_CALLBACK fdioGetPeerName(PRFileDesc *fd, PRNetAddr *addr) {
  *addr = fd->secret->peername;
  return PR_SUCCESS;
}

static PRStatus fdioGetSocketOption(PRFileDesc *fd, PRSocketOptionData *data) {
  if (data->option == PR_SockOpt_Nonblocking) {
    data->value.non_blocking = PR_FALSE;
    return PR_SUCCESS;
  }
  PR_SetError(PR_OPERATION_NOT_SUPPORTED_ERROR, 0);
  return PR_FAILURE;
}

PRFileDesc *newFDIO(int f) {
  static PRDescIdentity identity = PR_INVALID_IO_LAYER;
  if (identity == PR_INVALID_IO_LAYER) {
    check((identity = PR_GetUniqueIdentity("fdio")) != PR_INVALID_IO_LAYER);
  }

  PRFileDesc *fd;
  static const struct PRIOMethods layer_methods = {
    .file_type       = PR_DESC_LAYERED,
    .close           = fdioClose,
    .read            = fdioRead,
    .write           = fdioWrite,
    .shutdown        = fdioShutdown,
    .recv            = fdioRecv,
    .send            = fdioSend,
    .getpeername     = fdioGetPeerName,
    .getsocketoption = fdioGetSocketOption,
  };
  check(fd = PR_CreateIOLayerStub(identity, &layer_methods));

  struct PRFilePrivate *secret;
  check(secret = malloc(sizeof(struct PRFilePrivate)));
  memset(secret, 0, sizeof(struct PRFilePrivate));
  secret->f = f;
  char buf[256];
  struct sockaddr *addr = (struct sockaddr *)buf;
  socklen_t len = sizeof(buf);
  check(!getpeername(f, addr, &len));
  check(len < sizeof(buf));
  switch (addr->sa_family) {
  case AF_INET:
    secret->peername.inet.family = addr->sa_family;
    secret->peername.inet.port = ((struct sockaddr_in *)addr)->sin_port;
    memcpy(&secret->peername.inet.ip, &((struct sockaddr_in *)addr)->sin_addr,
           sizeof(PRUint32));
    break;
  case AF_INET6:
    secret->peername.ipv6.family = addr->sa_family;
    secret->peername.ipv6.port = ((struct sockaddr_in6 *)addr)->sin6_port;
    secret->peername.ipv6.flowinfo=((struct sockaddr_in6*)addr)->sin6_flowinfo;
    memcpy(&secret->peername.ipv6.ip, &((struct sockaddr_in6*)addr)->sin6_addr,
           sizeof(PRIPv6Addr));
    secret->peername.ipv6.scope_id=((struct sockaddr_in6*)addr)->sin6_scope_id;
    break;
  default:
    break;
  }
  fd->secret = secret;

  return fd;
}

static SECStatus certHandler(void *arg, PRFileDesc *fd, PRBool checksig,
                             PRBool is_server) {
  return SECSuccess;
}

struct SSL *newSSL(void) {
  struct SSL *ssl;
  check(ssl = malloc(sizeof(struct SSL)));
  initSSL(ssl);
  return ssl;
}

void initSSL(struct SSL *ssl) {
  static int initialized = 0;
  if (!initialized) {
    // Global initialization
    ++initialized;
    check(NSS_NoDB_Init(NULL) == SECSuccess);
    check(NSS_SetDomesticPolicy() == SECSuccess);
    atexit((void (*)(void))NSS_Shutdown);
  }
}

void destroySSL(struct SSL *ssl) {
}

void deleteSSL(struct SSL *ssl) {
  destroySSL(ssl);
  free(ssl);
}

struct SSLHandle *openSSLHandle(struct SSL *ssl, int fd) {
  PRFileDesc *handle;
  check(handle = newFDIO(fd));
  check(handle = SSL_ImportFD(NULL, handle));
  check(SSL_OptionSet(handle, SSL_SECURITY, PR_TRUE) == SECSuccess);
  check(SSL_OptionSet(handle, SSL_ENABLE_SSL2, PR_FALSE) == SECSuccess);
  check(SSL_OptionSet(handle, SSL_ENABLE_SSL3, PR_FALSE) == SECSuccess);
  check(SSL_OptionSet(handle, SSL_ENABLE_TLS, PR_TRUE) == SECSuccess);
  check(SSL_OptionSet(handle, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE) == SECSuccess);
  check(SSL_ResetHandshake(handle, PR_FALSE) == SECSuccess);
  check(SSL_AuthCertificateHook(handle, certHandler, NULL) == SECSuccess);
  do {
    PRInt32 rc = SSL_ForceHandshake(handle);
    if (rc != SECSuccess) {
      PRErrorCode err = PR_GetError();
      check(err == PR_WOULD_BLOCK_ERROR);
      continue;
    }
  } while (0);
  return (struct SSLHandle *)handle;
}

void closeSSLHandle(struct SSLHandle *handle) {
  struct PRFileDesc *h = (PRFileDesc *)handle;
  PR_Close(h);
  free(handle);
}

ssize_t SSLread(struct SSLHandle *handle, char *buf, size_t sz) {
  struct PRFileDesc *h = (PRFileDesc *)handle;
  PRInt32 rc = PR_Read(h, buf, sz);
  if (rc < 0) {
    PRErrorCode err = PR_GetError();
    if (err == PR_WOULD_BLOCK_ERROR) {
      errno = EAGAIN;
    } else if (err == PR_PENDING_INTERRUPT_ERROR) {
      errno = EINTR;
    } else {
      errno = EIO;
    }
    return -1;
  }
  return rc;
}

ssize_t SSLwrite(struct SSLHandle *handle, const char *buf, size_t sz) {
  struct PRFileDesc *h = (PRFileDesc *)handle;
  PRInt32 rc = PR_Write(h, buf, sz);
  if (rc < 0) {
    PRErrorCode err = PR_GetError();
    if (err == PR_WOULD_BLOCK_ERROR) {
      errno = EAGAIN;
    } else if (err == PR_PENDING_INTERRUPT_ERROR) {
      errno = EINTR;
    } else {
      errno = EIO;
    }
    return -1;
  }
  return rc;
}

#endif  // USE_NSS
